define( 
		[
		 "jquery"
		 ],
		 function( $ ) {
			
			$.tools = $.tools || {version: '1.2.7'};

			var tool;

			tool = $.tools.rangeinput = {

				conf: {
					min: 0,
					max: 100,		// as defined in the standard
					step: 'any', 	// granularity of the value. a non-zero float or int (or "any")
					steps: 0,
					value: 0,			
					precision: undefined,
					vertical: 0,
					keyboard: true,
					progress: false,
					speed: 100,

					// set to null if not needed
					css: {
						input:		'range',
						slider: 		'slider',
						progress: 	'progress',
						handle: 		'handle'					
					}

				} 
			};

		//{{{ fn.drag

			/* 
				FULL featured drag and drop. 0.7 kb minified, 0.3 gzipped. done.
				Who told d'n'd is rocket science? Usage:
				
				$(".myelement").drag({y: false}).on("drag", function(event, x, y) {
					// do your custom thing
				});
				 
				Configuration: 
					x: true, 		// enable horizontal drag
					y: true, 		// enable vertical drag 
					drag: true 		// true = perform drag, false = only fire events 
					
				Events: dragStart, drag, dragEnd. 
			*/
			var doc, draggable;

			$.fn.drag = function(conf) {

				// disable IE specialities
				document.ondragstart = function () { return false; };

				conf = $.extend({x: true, y: true, drag: true}, conf);

				doc = doc || $(document).on("mousedown mouseup", function(e) {

					var el = $(e.target);  

					// start 
					if (e.type == "mousedown" && el.data("drag")) {

						var offset = el.position(),
							 x0 = e.pageX - offset.left, 
							 y0 = e.pageY - offset.top,
							 start = true;    

						doc.on("mousemove.drag", function(e) {  
							var x = e.pageX -x0, 
								 y = e.pageY -y0,
								 props = {};

							if (conf.x) { props.left = x; }
							if (conf.y) { props.top = y; } 

							if (start) {
								el.trigger("dragStart");
								start = false;
							}
							if (conf.drag) { el.css(props); }
							el.trigger("drag", [y, x]);
							draggable = el;
						}); 

						e.preventDefault();

					} else {

						try {
							if (draggable) {  
								draggable.trigger("dragEnd");  
							}
						} finally { 
							doc.off("mousemove.drag");
							draggable = null; 
						}
					} 

				});

				return this.data("drag", true); 
			};	

		//}}}



			function round(value, precision) {
				var n = Math.pow(10, precision);
				return Math.round(value * n) / n;
			}

			// get hidden element's width or height even though it's hidden
			function dim(el, key) {
				var v = parseInt(el.css(key), 10);
				if (v) { return v; }
				var s = el[0].currentStyle; 
				return s && s.width && parseInt(s.width, 10);	
			}

			function hasEvent(el) {
				var e = el.data("events");
				return e && e.onSlide;
			}

			function RangeInput(input, conf) {

				// private variables
				var self = this,  
					 css = conf.css, 
					 root = $("<div><div/><a href='#'/></div>").data("rangeinput", self),	
					 vertical,		
					 value,			// current value
					 origo,			// handle's start point
					 len,				// length of the range
					 pos;				// current position of the handle		

				// create range	 
				input.before(root);	

				var handle = root.addClass(css.slider).find("a").addClass(css.handle), 	
					 progress = root.find("div").addClass(css.progress);

				// get (HTML5) attributes into configuration
				$.each("min,max,step,value".split(","), function(i, key) {
					var val = input.attr(key);
					if (parseFloat(val)) {
						conf[key] = parseFloat(val, 10);
					}
				});			   

				var range = conf.max - conf.min, 
					 step = conf.step == 'any' ? 0 : conf.step,
					 precision = conf.precision;

				if (precision === undefined) {
					precision = step.toString().split(".");
					precision = precision.length === 2 ? precision[1].length : 0;
				}  

				// Replace built-in range input (type attribute cannot be changed)
				if (input.attr("type") == 'range') {			
					var def = input.clone().wrap("<div/>").parent().html(),
						 clone = $(def.replace(/type/i, "type=text data-orig-type"));

					clone.val(conf.value);
					input.replaceWith(clone);
					input = clone;
				}

				input.addClass(css.input);

				var fire = $(self).add(input), fireOnSlide = true;


				/**
				 	The flesh and bone of this tool. All sliding is routed trough this.
					
					@param evt types include: click, keydown, blur and api (setValue call)
					@param isSetValue when called trough setValue() call (keydown, blur, api)
					
					vertical configuration gives additional complexity. 
				 */
				function slide(evt, x, val, isSetValue) { 

					// calculate value based on slide position
					if (val === undefined) {
						val = x / len * range;  

					// x is calculated based on val. we need to strip off min during calculation	
					} else if (isSetValue) {
						val -= conf.min;	
					}

					// increment in steps
					if (step) {
						val = Math.round(val / step) * step;
					}

					// count x based on value or tweak x if stepping is done
					if (x === undefined || step) {
						x = val * len / range;	
					}  

					// crazy value?
					if (isNaN(val)) { return self; }       

					// stay within range
					x = Math.max(0, Math.min(x, len));  
					val = x / len * range;   

					if (isSetValue || !vertical) {
						val += conf.min;
					}

					// in vertical ranges value rises upwards
					if (vertical) {
						if (isSetValue) {
							x = len -x;
						} else {
							val = conf.max - val;	
						}
					}	

					// precision
					val = round(val, precision); 

					// onSlide
					var isClick = evt.type == "click";
					if (fireOnSlide && value !== undefined && !isClick) {
						evt.type = "onSlide";           
						fire.trigger(evt, [val, x]); 
						if (evt.isDefaultPrevented()) { return self; }  
					}				

					// speed & callback
					var speed = isClick ? conf.speed : 0,
						 callback = isClick ? function()  {
							evt.type = "change";
							fire.trigger(evt, [val]);
						 } : null;

					if (vertical) {
						handle.animate({top: x}, speed, callback);
						if (conf.progress) { 
							progress.animate({height: len - x + handle.height() / 2}, speed);	
						}				

					} else {
						handle.animate({left: x}, speed, callback);
						if (conf.progress) { 
							progress.animate({width: x + handle.width() / 2}, speed); 
						}
					}

					// store current value
					value = val; 
					pos = x;			 

					// se input field's value
					input.val(val);

					return self;
				} 


				$.extend(self, {  

					getValue: function() {
						return value;	
					},

					setValue: function(val, e) {
						init();
						return slide(e || $.Event("api"), undefined, val, true); 
					}, 			  

					getConf: function() {
						return conf;	
					},

					getProgress: function() {
						return progress;	
					},

					getHandle: function() {
						return handle;	
					},			

					getInput: function() {
						return input;	
					}, 

					step: function(am, e) {
						e = e || $.Event();
						var step = conf.step == 'any' ? 1 : conf.step;
						self.setValue(value + step * (am || 1), e);	
					},

					// HTML5 compatible name
					stepUp: function(am) { 
						return self.step(am || 1);
					},

					// HTML5 compatible name
					stepDown: function(am) { 
						return self.step(-am || -1);
					}

				});

				// callbacks
				$.each("onSlide,change".split(","), function(i, name) {

					// from configuration
					if ($.isFunction(conf[name]))  {
						$(self).on(name, conf[name]);	
					}

					// API methods
					self[name] = function(fn) {
						if (fn) { $(self).on(name, fn); }
						return self;	
					};
				}); 


				// dragging		                                  
				handle.drag({drag: false}).on("dragStart", function() {

					/* do some pre- calculations for seek() function. improves performance */			
					init();

					// avoid redundant event triggering (= heavy stuff)
					fireOnSlide = hasEvent($(self)) || hasEvent(input);


				}).on("drag", function(e, y, x) {        

					if (input.is(":disabled")) { return false; } 
					slide(e, vertical ? y : x); 

				}).on("dragEnd", function(e) {
					if (!e.isDefaultPrevented()) {
						e.type = "change";
						fire.trigger(e, [value]);	 
					}

				}).click(function(e) {
					return e.preventDefault();	 
				});		

				// clicking
				root.click(function(e) { 
					if (input.is(":disabled") || e.target == handle[0]) { 
						return e.preventDefault(); 
					}				  
					init(); 
					var fix = vertical ? handle.height() / 2 : handle.width() / 2;
					slide(e, vertical ? len-origo-fix + e.pageY  : e.pageX -origo -fix);  
				});

				if (conf.keyboard) {

					input.keydown(function(e) {

						if (input.attr("readonly")) { return; }

						var key = e.keyCode,
							 up = $([75, 76, 38, 33, 39]).index(key) != -1,
							 down = $([74, 72, 40, 34, 37]).index(key) != -1;					 

						if ((up || down) && !(e.shiftKey || e.altKey || e.ctrlKey)) {

							// UP: 	k=75, l=76, up=38, pageup=33, right=39			
							if (up) {
								self.step(key == 33 ? 10 : 1, e);

							// DOWN:	j=74, h=72, down=40, pagedown=34, left=37
							} else if (down) {
								self.step(key == 34 ? -10 : -1, e); 
							} 
							return e.preventDefault();
						} 
					});
				}


				input.blur(function(e) {	
					var val = $(this).val();
					if (val !== value) {
						self.setValue(val, e);
					}
				});    


				// HTML5 DOM methods
				$.extend(input[0], { stepUp: self.stepUp, stepDown: self.stepDown});


				// calculate all dimension related stuff
				function init() { 
				 	vertical = conf.vertical || dim(root, "height") > dim(root, "width");

					if (vertical) {
						len = dim(root, "height") - dim(handle, "height");
						origo = root.offset().top + len; 

					} else {
						len = dim(root, "width") - dim(handle, "width");
						origo = root.offset().left;	  
					} 	  
				}

				function begin() {
					init();	
					self.setValue(conf.value !== undefined ? conf.value : conf.min);
				} 
				begin();

				// some browsers cannot get dimensions upon initialization
				if (!len) {  
					$(window).load(begin);
				}
			}

			$.expr[':'].range = function(el) {
				var type = el.getAttribute("type");
				return type && type == 'range' || !!$(el).filter("input").data("rangeinput");
			};


			// jQuery plugin implementation
			$.fn.rangeinput = function(conf) {

				// already installed
				if (this.data("rangeinput")) { return this; } 

				// extend configuration with globals
				conf = $.extend(true, {}, tool.conf, conf);		

				var els;

				this.each(function() {				
					var el = new RangeInput($(this), $.extend(true, {}, conf));		 
					var input = el.getInput().data("rangeinput", el);
					els = els ? els.add(input) : input;	
				});		

				return els ? els : this; 
			};	
		} );